package vcsn;

import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Vector;
import java.util.concurrent.Semaphore;

import javax.swing.JOptionPane;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.api.errors.PatchApplyException;
import org.eclipse.jgit.diff.DiffEntry;
import org.eclipse.jgit.lib.ObjectReader;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevTree;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.treewalk.CanonicalTreeParser;
import org.eclipse.jgit.treewalk.TreeWalk;
import vcsn.Config.SyncStatus;
import vcsn.utils.ExportImportHandler;
import vcsn.utils.Util;
import vcsn.entities.AddCastingOperation;
import vcsn.entities.AddCommentOperation;
import vcsn.entities.CommitPatch;
import vcsn.entities.ConflictOperation;
import vcsn.entities.DropInterfaceTypeOperation;
import vcsn.entities.FileLock;
import vcsn.entities.LastAppliedCommitHashList;
import vcsn.entities.PeerList;
import vcsn.entities.RefineParameterOperation;
import vcsn.entities.RelationMapDiff;
import vcsn.entities.RemoveKeyWordOperation;
import vcsn.entities.RenameOperation;
import vcsn.entities.VectorClock;

public class ServerThread extends Thread {
	public Socket socket;
	public DataInputStream in;
	public static Semaphore mutex = new Semaphore(1, true);
	public String modifiedFileList;
	private CommitPatch incomingPatch;
	
	public ServerThread(Socket s) {
		socket = s;
		try {
			in = new DataInputStream(s.getInputStream());
		} catch (IOException e) {
			e.printStackTrace();
		}
		this.start();
	}
	
	@Override
	public void run() {
		try {			
			long checksum = in.readLong();
			System.out.println("Received checksum : " + checksum);
			
			File f = vcsn.utils.Util.createTempFile();
			FileOutputStream fos = new FileOutputStream(f);
			for(long i=0;i<checksum;i++){
				fos.write(in.read());
			}
			fos.flush();
			fos.close();
			in.close();
			socket.close();
			
			DataInputStream dataInputStream = new DataInputStream(new FileInputStream(f));
			byte[] stringBytes;
			
			// Read header type
			int headerType = dataInputStream.readInt();

			// Read project name
			int readStringLength = dataInputStream.readInt();
			stringBytes = new byte[readStringLength];
			dataInputStream.read(stringBytes, 0, readStringLength);
			String projectname = new String(stringBytes, Config.charSet);
			IProject project = ResourcesPlugin.getWorkspace().getRoot().getProject(projectname);
			
			// Read incoming Clock
			readStringLength = dataInputStream.readInt();
			stringBytes = new byte[readStringLength];
			dataInputStream.read(stringBytes, 0, readStringLength);
			String incomingClockString = new String(stringBytes, Config.charSet);
			
			// Read sender address
			readStringLength = dataInputStream.readInt();
			stringBytes = new byte[readStringLength];
			dataInputStream.read(stringBytes, 0, readStringLength);
			String senderIPAddress = new String(stringBytes, Config.charSet);
			
			// only process while the header defined project identifier is an existing project
			if(project.exists()){
				Configuration configure = new Configuration(project.getLocation().toString(), project.getName());
				int acknowledgmentType = Config.AcknowledgmentType.NOTYPE;
				// Predefine the context file that received and the data output stream for further reuse,
				long  receivedFileLength;
				File receivedfile = null;
				DataOutputStream dataOutputStream;
				
				// Context file only exist while header is not an acknowledgment type
				if(headerType != Config.HeaderType.ACKNOWLEDGMENT){	
					receivedFileLength = dataInputStream.readLong();
					receivedfile = Util.createTempFile();
					dataOutputStream = new DataOutputStream(new FileOutputStream(receivedfile));
					for(long i = 0; i < receivedFileLength; i++){
						dataOutputStream.write(dataInputStream.read());
					}
					dataOutputStream.flush();
					dataOutputStream.close();
				}else{
					acknowledgmentType = dataInputStream.readInt();
				}
				
				// determine the incoming package will be dropped by the status and the headertype
				if(Config.status == SyncStatus.OFFLINE){
					return;
				}else if((headerType == Config.HeaderType.PEER || acknowledgmentType == Config.HeaderType.ONLINESYNCREQUEST)
						&& Config.status != SyncStatus.ONLINE){
					return;
				}else if((acknowledgmentType == Config.AcknowledgmentType.PEER || acknowledgmentType == Config.AcknowledgmentType.ONLINESYNCRESPONSE) 
						&& Config.status != SyncStatus.SYNC){
					return;
				}

				// Merge incoming Clock to local Clock and increment if it is not smaller 
				VectorClock incomingClock = VectorClock.toVectorClock(incomingClockString);
				VectorClock localClock = ExportImportHandler.importVectorClock(System.getProperty(Config.homeDirectoryNotation) + File.separator + Config.localClockFile, project.getName());

				// check if the incoming package contain a smaller clock, drop it as a out dated package.
				if(VectorClock.compare(incomingClock, localClock) == VectorClock.VectorComparison.SMALLER && headerType != Config.HeaderType.PEER){
					return;
				}
				
				localClock = VectorClock.max(localClock, incomingClock);
				localClock.incrementClock(configure.getUserName() + Config.keyConnector + configure.getUserEmail());
				ExportImportHandler.exportVectorClock(localClock, project.getName());
				
				
				
				mutex.acquire();
				Git git = Git.open(new File(project.getLocation().toString()));
				Repository repository = git.getRepository();
				// Git peer
				PeerList peerListHandler;
				// diffpatch
				String commitAuthor;
				String incomingCommitHashString;
				long receivedPatchLength;
				File receivedPatch;
				// get local elements
				LastAppliedCommitHashList commithashList;
				// client post back elements
				ClientThread client;
				String localAddress;
				
				File fDiffPatch;
				switch(headerType){
					case Config.HeaderType.PEER:
					
						peerListHandler = new PeerList();
						Vector<String> incomingPeerList = peerListHandler.getPeerList(receivedfile.getAbsolutePath());
						if(peerListHandler.comparePeerList(incomingPeerList, peerListHandler.getPeerList(project.getLocation().toString() + File.separator + Config.peerFile))){
							peerListHandler.updateLocalPeerList(project.getLocation().toString() + File.separator + Config.peerFile, incomingPeerList);
							localClock.incrementClock(configure.getUserName() + Config.keyConnector + configure.getUserEmail());
							ExportImportHandler.exportVectorClock(localClock, project.getName());
							
							git.add().addFilepattern(Config.peerFile).call();
							git.commit().setAuthor(configure.getUserName(), configure.getUserEmail()).setMessage(Config.peerFileAppliedMsg).call();
						}
						commithashList = ExportImportHandler.importLastAppliedCommitHashList(System.getProperty(Config.homeDirectoryNotation) + File.separator + Config.lastAppliedCommitHashListFile, project.getName());
						
//						fDiffPatch = generateDiffPatch(git, repository.getRef(Config.OriginMasterHash).getObjectId().getName(), git.getRepository().getRef(Config.HEADHash).getObjectId().getName());
						fDiffPatch = generateDiffPatch(git, commithashList.get(configure.getUserName()), git.getRepository().getRef(Config.HEADHash).getObjectId().getName());
						
						updateLastAppliedCommitHash(System.getProperty(Config.homeDirectoryNotation) + File.separator + Config.lastAppliedCommitHashListFile, configure.getUserName(), repository.getRef(Config.HEADHash).getObjectId().getName(), project.getName());

						//if(incomingPeerList.contains(senderIPAddress)){
							// Acknowledgment post back
							client = new ClientThread();
							localAddress = client.getLocalAddress(new File(project.getLocation().toString() + File.separator + Config.peerFile));
							client.writeHeaderIntoPackageFile(projectname, localClock, localAddress, Config.HeaderType.ACKNOWLEDGMENT);
							client.writeAcknowledgmentHeaderIntoPackageFile(Config.AcknowledgmentType.PEER);
							client.writeFileIntoPackageFile(fDiffPatch);
							client.writeStringIntoPackageFile(configure.getUserName());
							client.writeStringIntoPackageFile(repository.getRef(Config.HEADHash).getObjectId().getName());
							client.writeFileIntoPackageFile(new File(System.getProperty(Config.homeDirectoryNotation) + File.separator + Config.lastAppliedCommitHashListFile));
							client.writeIntergerIntoPackageFile((incomingPeerList.contains(senderIPAddress)) ? 1 : 0);
							client.flush(senderIPAddress);
							client.disposePackage();
							
							// update head hash to all other peers
							client.writeHeaderIntoPackageFile(projectname, localClock, localAddress, Config.HeaderType.ACKNOWLEDGMENT);
							client.writeAcknowledgmentHeaderIntoPackageFile(Config.AcknowledgmentType.HashHeadSYNC);
							client.writeStringIntoPackageFile(configure.getUserName());
							client.writeStringIntoPackageFile(repository.getRef(Config.HEADHash).getObjectId().getName());
							for(String peer : incomingPeerList){
								if(!peer.equals(localAddress) && !peer.equals(senderIPAddress))
								client.flush(peer);
							}
							client.disposePackage();
						//}
						break;
					case Config.HeaderType.ONLINESYNCREQUEST:
						commithashList = ExportImportHandler.importLastAppliedCommitHashList(receivedfile.getAbsolutePath(), project.getName());
												
						fDiffPatch = generateDiffPatch(git, commithashList.get(configure.getUserName()), git.getRepository().getRef(Config.HEADHash).getObjectId().getName());
						
						// Acknowledgment post back
						client = new ClientThread();
						localAddress = client.getLocalAddress(new File(project.getLocation().toString() + File.separator + Config.peerFile));
						client.writeHeaderIntoPackageFile(projectname, localClock, localAddress, Config.HeaderType.ACKNOWLEDGMENT);
						client.writeAcknowledgmentHeaderIntoPackageFile(Config.AcknowledgmentType.ONLINESYNCRESPONSE);
						client.writeFileIntoPackageFile(fDiffPatch);
						client.writeStringIntoPackageFile(configure.getUserName());
						client.writeStringIntoPackageFile(repository.getRef(Config.HEADHash).getObjectId().getName());
						client.writeFileIntoPackageFile(new File(System.getProperty(Config.homeDirectoryNotation) + File.separator + Config.lastAppliedCommitHashListFile));
						client.flush(senderIPAddress);
						client.disposePackage();
						break;
					case Config.HeaderType.FILELOCK:
						// read sender name
						readStringLength = dataInputStream.readInt();
						stringBytes = new byte[readStringLength];
						dataInputStream.read(stringBytes, 0, readStringLength);
						commitAuthor = new String(stringBytes, Config.charSet);

						// read commit hash
						readStringLength = dataInputStream.readInt();
						stringBytes = new byte[readStringLength];
						dataInputStream.read(stringBytes, 0, readStringLength);
						incomingCommitHashString = new String(stringBytes, Config.charSet);
						
						updatefileLock(project, receivedfile, localClock);
						
						git.add().addFilepattern(Config.lockFile).call();
						git.commit().setAuthor(configure.getUserName(), configure.getUserEmail()).setMessage(Config.LockfileAppliedMsg).call();
						
						updateLastAppliedCommitHash(System.getProperty(Config.homeDirectoryNotation) + File.separator + Config.lastAppliedCommitHashListFile, configure.getUserName(), repository.getRef(Config.HEADHash).getObjectId().getName(), project.getName());
						updateLastAppliedCommitHash(System.getProperty(Config.homeDirectoryNotation) + File.separator + Config.lastAppliedCommitHashListFile, commitAuthor, incomingCommitHashString, project.getName());
						
						client = new ClientThread();
						localAddress = client.getLocalAddress(new File(project.getLocation().toString() + File.separator + Config.peerFile));
						client.writeHeaderIntoPackageFile(projectname, localClock, localAddress, Config.HeaderType.ACKNOWLEDGMENT);
						client.writeAcknowledgmentHeaderIntoPackageFile(Config.AcknowledgmentType.HashHeadSYNC);
						client.writeStringIntoPackageFile(configure.getUserName());
						client.writeStringIntoPackageFile(repository.getRef(Config.HEADHash).getObjectId().getName());
						client.flush(new File(project.getProject().getLocation().toString() + File.separator + Config.peerFile));
						client.disposePackage();
						break;
					case Config.HeaderType.ACKNOWLEDGMENT:
						switch(acknowledgmentType){
							case Config.AcknowledgmentType.PEER:
								// read incoming patch for apply or inspect
								receivedPatchLength = dataInputStream.readLong();
								receivedPatch = Util.createTempCommitPatchFile();
								dataOutputStream = new DataOutputStream(new FileOutputStream(receivedPatch));
								for(long i = 0; i < receivedPatchLength; i++){
									dataOutputStream.write(dataInputStream.read());
								}
								dataOutputStream.flush();
								dataOutputStream.close();
								
								// read sender name
								readStringLength = dataInputStream.readInt();
								stringBytes = new byte[readStringLength];
								dataInputStream.read(stringBytes, 0, readStringLength);
								commitAuthor = new String(stringBytes, Config.charSet);
								
								// read commit hash
								readStringLength = dataInputStream.readInt();
								stringBytes = new byte[readStringLength];
								dataInputStream.read(stringBytes, 0, readStringLength);
								incomingCommitHashString = new String(stringBytes, Config.charSet);
								
								// read lastAppliedCommitHashListFile
								receivedFileLength = dataInputStream.readLong();
								receivedfile = Util.createTempFile();
								dataOutputStream = new DataOutputStream(new FileOutputStream(receivedfile));
								for(long i = 0; i < receivedFileLength; i++){
									dataOutputStream.write(dataInputStream.read());
								}
								dataOutputStream.flush();
								dataOutputStream.close();
								
								int isRemoveAction = dataInputStream.readInt();
								
								try{								
									git.apply().setPatch(new DataInputStream(new FileInputStream(receivedPatch))).call();
									git.add().addFilepattern(".").call();
									git.commit().setAuthor(configure.getUserName(), configure.getUserEmail()).setMessage(Config.patchSyncMsg).call();
									git.push().setRemote(configure.getRemoteURI()).setForce(true).call();
									
									updateLastAppliedCommitHash(System.getProperty(Config.homeDirectoryNotation) + File.separator + Config.lastAppliedCommitHashListFile, configure.getUserName(), repository.getRef(Config.HEADHash).getObjectId().getName(), project.getName());
									updateLastAppliedCommitHash(receivedfile.getAbsolutePath(), commitAuthor, incomingCommitHashString, project.getName());
									
									RelationMapBuilder rmbuilder = new RelationMapBuilder(project,"");
									rmbuilder.cleanbuild();
									
									// Post-sync broadcast self head.
									client = new ClientThread();
									localAddress = client.getLocalAddress(new File(project.getLocation().toString() + File.separator + Config.peerFile));
									client.writeHeaderIntoPackageFile(projectname, localClock, localAddress, Config.HeaderType.ACKNOWLEDGMENT);
									client.writeAcknowledgmentHeaderIntoPackageFile(Config.AcknowledgmentType.POSTPEERSYNC);
									client.writeStringIntoPackageFile(configure.getUserName());
									client.writeStringIntoPackageFile(repository.getRef(Config.HEADHash).getObjectId().getName());
									client.flush(new File(project.getProject().getLocation().toString() + File.separator + Config.peerFile));
									client.disposePackage();
									Config.status = isRemoveAction > 0 ? SyncStatus.ONLINE : SyncStatus.OFFLINE;
									JOptionPane.showMessageDialog(null, Config.syncStatusMsg + Config.status, Config.syncDialogTitle, JOptionPane.INFORMATION_MESSAGE);
								}catch(PatchApplyException pae){
									JOptionPane.showMessageDialog(null, "peer sync fail", Config.syncDialogTitle, JOptionPane.INFORMATION_MESSAGE);
									pae.printStackTrace();//.openInformation(PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell(), "Error", "Commit Patch " + incomingPatch.getPatchHash() + " in file: " + incomingPatch.getPatchPath() + " fail to apply with disable handled conflicts. Please apply it in manual.");
									mutex.release();
								}
								break;
							case Config.AcknowledgmentType.ONLINESYNCRESPONSE:
								// read incoming patch for apply or inspect
								receivedPatchLength = dataInputStream.readLong();
								receivedPatch = Util.createTempCommitPatchFile();
								dataOutputStream = new DataOutputStream(new FileOutputStream(receivedPatch));
								for(long i = 0; i < receivedPatchLength; i++){
									dataOutputStream.write(dataInputStream.read());
								}
								dataOutputStream.flush();
								dataOutputStream.close();
								
								// read sender name
								readStringLength = dataInputStream.readInt();
								stringBytes = new byte[readStringLength];
								dataInputStream.read(stringBytes, 0, readStringLength);
								commitAuthor = new String(stringBytes, Config.charSet);
								
								// read commit hash
								readStringLength = dataInputStream.readInt();
								stringBytes = new byte[readStringLength];
								dataInputStream.read(stringBytes, 0, readStringLength);
								incomingCommitHashString = new String(stringBytes, Config.charSet);
								
								// read lastAppliedCommitHashListFile
								receivedFileLength = dataInputStream.readLong();
								receivedfile = Util.createTempFile();
								dataOutputStream = new DataOutputStream(new FileOutputStream(receivedfile));
								for(long i = 0; i < receivedFileLength; i++){
									dataOutputStream.write(dataInputStream.read());
								}
								dataOutputStream.flush();
								dataOutputStream.close();
								
								try{
									git.apply().setPatch(new DataInputStream(new FileInputStream(receivedPatch))).call();
									git.add().addFilepattern(".").call();
									git.commit().setAuthor(configure.getUserName(), configure.getUserEmail()).setMessage(Config.patchSyncMsg).call();
									
									updateLastAppliedCommitHash(System.getProperty(Config.homeDirectoryNotation) + File.separator + Config.lastAppliedCommitHashListFile, configure.getUserName(), repository.getRef(Config.HEADHash).getObjectId().getName(), project.getName());
									updateLastAppliedCommitHash(receivedfile.getAbsolutePath(), commitAuthor, incomingCommitHashString, project.getName());
									
									Config.status = SyncStatus.ONLINE;
									JOptionPane.showMessageDialog(null, "Sync successful! You are now online", "Synchronize status", JOptionPane.INFORMATION_MESSAGE);
								}catch(PatchApplyException pae){
									pae.printStackTrace();//.openInformation(PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell(), "Error", "Commit Patch " + incomingPatch.getPatchHash() + " in file: " + incomingPatch.getPatchPath() + " fail to apply with disable handled conflicts. Please apply it in manual.");
									mutex.release();
								}
								break;
							case Config.AcknowledgmentType.POSTPEERSYNC:
								// read sender name
								readStringLength = dataInputStream.readInt();
								stringBytes = new byte[readStringLength];
								dataInputStream.read(stringBytes, 0, readStringLength);
								commitAuthor = new String(stringBytes, Config.charSet);
								
								// read commit hash
								readStringLength = dataInputStream.readInt();
								stringBytes = new byte[readStringLength];
								dataInputStream.read(stringBytes, 0, readStringLength);
								incomingCommitHashString = new String(stringBytes, Config.charSet);
								
								updateLastAppliedCommitHash(System.getProperty(Config.homeDirectoryNotation) + File.separator + Config.lastAppliedCommitHashListFile, commitAuthor, incomingCommitHashString, project.getName());
//								try{
////									git.pull().setRebase(true).call();
//									git.pull().call();
//								}catch(GitAPIException gapex){
//									gapex.printStackTrace();
//								}
								break;
							case Config.AcknowledgmentType.HashHeadSYNC:
								// read sender name
								readStringLength = dataInputStream.readInt();
								stringBytes = new byte[readStringLength];
								dataInputStream.read(stringBytes, 0, readStringLength);
								commitAuthor = new String(stringBytes, Config.charSet);
								
								// read commit hash
								readStringLength = dataInputStream.readInt();
								stringBytes = new byte[readStringLength];
								dataInputStream.read(stringBytes, 0, readStringLength);
								incomingCommitHashString = new String(stringBytes, Config.charSet);
								
								updateLastAppliedCommitHash(System.getProperty(Config.homeDirectoryNotation) + File.separator + Config.lastAppliedCommitHashListFile, commitAuthor, incomingCommitHashString, project.getName());
								
								break;
//							case Config.AcknowledgmentType.DIFFPATCHCONFLICTRESOLVED:
//								readStringLength = dataInputStream.readInt();
//								stringBytes = new byte[readStringLength];
//								dataInputStream.read(stringBytes, 0, readStringLength);
//								incomingCommitHashString = new String(stringBytes, Config.charSet);
//								break;
							case Config.AcknowledgmentType.POSTPATCHSYNC:
								// read sender name
								readStringLength = dataInputStream.readInt();
								stringBytes = new byte[readStringLength];
								dataInputStream.read(stringBytes, 0, readStringLength);
								commitAuthor = new String(stringBytes, Config.charSet);
								
								// read commit hash
								readStringLength = dataInputStream.readInt();
								stringBytes = new byte[readStringLength];
								dataInputStream.read(stringBytes, 0, readStringLength);
								incomingCommitHashString = new String(stringBytes, Config.charSet);
								
								readStringLength = dataInputStream.readInt();
								stringBytes = new byte[readStringLength];
								dataInputStream.read(stringBytes, 0, readStringLength);
								String refCommitHashString = new String(stringBytes, Config.charSet);
								
								List<CommitPatch> existingPatchList = ExportImportHandler.importCommitPatch(System.getProperty(Config.homeDirectoryNotation) + File.separator + Config.incomingCommitPatchListFile, project.getName());
								for(CommitPatch cp : existingPatchList){
									if(cp.getPatchHash().equals(refCommitHashString)){
										updateLastAppliedCommitHash(System.getProperty(Config.homeDirectoryNotation) + File.separator + Config.lastAppliedCommitHashListFile, commitAuthor, incomingCommitHashString, project.getName());
										break;
									}
								}
								
								break;
							case Config.AcknowledgmentType.NOTYPE:
								break;
						}
						break;
					case Config.HeaderType.COMMITPATCH:
						// read sender name
						readStringLength = dataInputStream.readInt();
						stringBytes = new byte[readStringLength];
						dataInputStream.read(stringBytes, 0, readStringLength);
						commitAuthor = new String(stringBytes, Config.charSet);
						
						// read commit hash
						readStringLength = dataInputStream.readInt();
						stringBytes = new byte[readStringLength];
						dataInputStream.read(stringBytes, 0, readStringLength);
						incomingCommitHashString = new String(stringBytes, Config.charSet);
						
						LastAppliedCommitHashList commitHashList = ExportImportHandler.importLastAppliedCommitHashList(System.getProperty(Config.homeDirectoryNotation) + File.separator + Config.lastAppliedCommitHashListFile, project.getName());
						String lastappliedCommithashString = commitHashList.get(commitAuthor);
						
						// post back
						client = new ClientThread();
						localAddress = client.getLocalAddress(new File(project.getLocation().toString() + File.separator + Config.peerFile));
						client.writeHeaderIntoPackageFile(projectname, localClock, localAddress, Config.HeaderType.DIFFPATCHREQUEST);
						client.writeFileIntoPackageFile(receivedfile);
						client.writeStringIntoPackageFile(lastappliedCommithashString);
						client.writeStringIntoPackageFile(incomingCommitHashString);
						client.writeFileIntoPackageFile(new File(project.getLocation().toString() + File.separator + Config.lockFile));
						client.flush(senderIPAddress);
						client.disposePackage();
						
//						readStringLength = dataInputStream.readInt();
//						stringBytes = new byte[readStringLength];
//						dataInputStream.read(stringBytes, 0, readStringLength);
//						modifiedFileList = new String(stringBytes, Config.charSet);
						
						// read incoming patch for apply or inspect
//						receivedPatchLength = dataInputStream.readLong();
//						receivedPatch = Util.createTempCommitPatchFile();
//						dataOutputStream = new DataOutputStream(new FileOutputStream(receivedPatch));
//						for(long i = 0; i < receivedPatchLength; i++){
//							dataOutputStream.write(dataInputStream.read());
//						}
//						dataOutputStream.flush();
//						dataOutputStream.close();
						
						
						// Further calculation for different. Link the commit with the CommitPatch for use if the commit is applied.
						
						// TODO: Need to fix the eternal exception throw for hunk header[1,1->1,1]. problem may be considered causing by the same master head while debugging in same host.  
//						try{
//							git.apply().setPatch(new DataInputStream(new FileInputStream(receivedPatch))).call();
//							//git.add().addFilepattern(".").call();
//							for(String modifiedfile : modifiedFileList.split(Config.separator)){
//								git.add().addFilepattern(modifiedfile).call();
//								System.out.println("Add file: " + modifiedfile);
//							}
//							git.commit().setAuthor(Configuration.getAuthor(), Configuration.getEmail()).setMessage(String.format(Config.patchAppliedMsg, incomingCommitHashString)).call();
//							
//							// set applied commit hash history
//							LastAppliedCommitHashList commithashList = ExportImportHandler.importLastAppliedCommitHashList(System.getProperty(Config.homeDirectoryNotation) + File.separator + Config.lastAppliedCommitHashListFile);
//							if(commithashList == null){
//								commithashList = new LastAppliedCommitHashList();
//							}
//							if(!commithashList.containsKey(commitAuthor)){
//								commithashList.setCommitHash(commitAuthor, incomingCommitHashString);
//								ExportImportHandler.exportLastAppliedCommitHashList(commithashList);
//							}
//							
//							// Detect conflicts
//							List<RelationMapDiff> incomingRelationMapDiffList = ExportImportHandler.importRelationMapDiffFromXML(receivedfile.getAbsolutePath());
//							CommitPatch incomingPatch = new CommitPatch();
//							incomingPatch.setAuthor(commitAuthor);
//							incomingPatch.setRelationDiffPath(receivedfile.getAbsolutePath());
//							incomingPatch.setPatchHash(incomingCommitHashString);
//							incomingPatch.setPatchPath(receivedPatch.getAbsolutePath());
//							incomingPatch.setSenderAddr(senderIPAddress);
//							new RelationMapConflictDetector(project, incomingRelationMapDiffList, incomingPatch);
////							RelationMapBuilder builder = new RelationMapBuilder(project, String.format(Config.patchConflictsResolvedMsg, incomingCommitHashString));
////							builder.buildAndBroadcastCommitPatch();
//						}catch(PatchApplyException pae){
//							pae.printStackTrace();//.openInformation(PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell(), "Error", "Commit Patch " + incomingPatch.getPatchHash() + " in file: " + incomingPatch.getPatchPath() + " fail to apply with disable handled conflicts. Please apply it in manual.");
//							mutex.release();
//						}
						break;
					case Config.HeaderType.DIFFPATCHREQUEST:
						// read commit hash
						readStringLength = dataInputStream.readInt();
						stringBytes = new byte[readStringLength];
						dataInputStream.read(stringBytes, 0, readStringLength);
						String fromCommitHashString = new String(stringBytes, Config.charSet);
						
						// read commit hash
						readStringLength = dataInputStream.readInt();
						stringBytes = new byte[readStringLength];
						dataInputStream.read(stringBytes, 0, readStringLength);
						String toCommitHashString = new String(stringBytes, Config.charSet);
						
						fDiffPatch = generateDiffPatch(git, fromCommitHashString, toCommitHashString);
						
						client = new ClientThread();
						localAddress = client.getLocalAddress(new File(project.getLocation().toString() + File.separator + Config.peerFile));
						client.writeHeaderIntoPackageFile(projectname, localClock, localAddress, Config.HeaderType.DIFFPATCH);
						client.writeFileIntoPackageFile(receivedfile);
						client.writeStringIntoPackageFile(configure.getUserName());
						client.writeStringIntoPackageFile(toCommitHashString);
						client.writeStringIntoPackageFile(modifiedFileList);
						client.writeFileIntoPackageFile(fDiffPatch);
						client.flush(senderIPAddress);
						client.disposePackage();
						break;
					case Config.HeaderType.DIFFPATCH:
						// read sender name
						readStringLength = dataInputStream.readInt();
						stringBytes = new byte[readStringLength];
						dataInputStream.read(stringBytes, 0, readStringLength);
						commitAuthor = new String(stringBytes, Config.charSet);
						
						// read commit hash
						readStringLength = dataInputStream.readInt();
						stringBytes = new byte[readStringLength];
						dataInputStream.read(stringBytes, 0, readStringLength);
						incomingCommitHashString = new String(stringBytes, Config.charSet);
						
						// read list of changed files' name
						readStringLength = dataInputStream.readInt();
						stringBytes = new byte[readStringLength];
						dataInputStream.read(stringBytes, 0, readStringLength);
						modifiedFileList = new String(stringBytes, Config.charSet);
						
						// read incoming patch for apply or inspect
						receivedPatchLength = dataInputStream.readLong();
						receivedPatch = Util.createTempCommitPatchFile();
						dataOutputStream = new DataOutputStream(new FileOutputStream(receivedPatch));
						for(long i = 0; i < receivedPatchLength; i++){
							dataOutputStream.write(dataInputStream.read());
						}
						dataOutputStream.flush();
						dataOutputStream.close();
						
						
						// Further calculation for different. Link the commit with the CommitPatch for use if the commit is applied.
						
						// TODO: Need to fix the eternal exception throw for hunk header[1,1->1,1]. problem may be considered causing by the same master head while debugging in same host.  
						try{
							git.apply().setPatch(new DataInputStream(new FileInputStream(receivedPatch))).call();
							//git.add().addFilepattern(".").call();
							for(String modifiedfile : modifiedFileList.split(Config.separator)){
								if(modifiedfile.equals(Config.patchDeletion)){
									RevWalk walk = new RevWalk(repository);
									Ref head = repository.getRef(Config.HEADHash);
							        RevCommit commit = walk.parseCommit(head.getObjectId());
							        RevTree tree = commit.getTree();
									TreeWalk treeWalk = new TreeWalk(repository);
							        treeWalk.addTree(tree);
							        treeWalk.setRecursive(true);
							        while (treeWalk.next()) {
							            if(!new File(project.getProject().getLocation().toString() + File.separator + treeWalk.getPathString()).exists()){
							            	git.rm().addFilepattern(treeWalk.getPathString()).call();
							            }
							        }
								}else{
									git.add().addFilepattern(modifiedfile).call();
								}
//								System.out.println("Add file: " + modifiedfile);
							}
							git.commit().setAuthor(configure.getUserName(), configure.getUserEmail()).setMessage(String.format(Config.patchAppliedMsg, incomingCommitHashString)).call();
							
							// set applied commit hash history
							updateLastAppliedCommitHash(System.getProperty(Config.homeDirectoryNotation) + File.separator + Config.lastAppliedCommitHashListFile, commitAuthor, incomingCommitHashString, project.getName());
							
							// Detect conflicts
							List<RelationMapDiff> incomingRelationMapDiffList = ExportImportHandler.importRelationMapDiffFromXML(receivedfile.getAbsolutePath());
							incomingPatch = new CommitPatch();
							incomingPatch.setAuthor(commitAuthor);
							incomingPatch.setRelationDiffPath(receivedfile.getAbsolutePath());
							incomingPatch.setPatchHash(incomingCommitHashString);
							incomingPatch.setPatchPath(receivedPatch.getAbsolutePath());
							incomingPatch.setSenderAddr(senderIPAddress);
							new RelationMapConflictDetector(project, incomingRelationMapDiffList, incomingPatch);
							Vector<String> processFileList = getProcessingFileListFromConflictOperationList(incomingPatch.getConflictList());
							HashMap<String, String> lockedFileList = new FileLock(project.getLocation().toString() + File.separator + Config.lockFile).getlockedPair();
							for(String file : processFileList){
								if(lockedFileList.keySet().contains(file.replace("/" + project.getName(), "")) && !(lockedFileList.get(file.replace("/" + project.getName(), "")).equals(configure.getUserName()))){
									continue;
								}
								List<ConflictOperation> conflictOperationList = getConflictOperationListInSingleFile(incomingPatch.getConflictList(), file);
								IFile sourceFile = project.getFile(file.replace("/" + project.getName(), ""));
								
//								ConflictOperationManager operationManager = new ConflictOperationManager(new File(project.getLocation().toString().replace("/" + project.getName(), file)),getConflictOperationListInSingleFile(incomingPatch.getConflictList(), file));
								ConflictOperationManager operationManager = new ConflictOperationManager(sourceFile.getLocation().toFile(),conflictOperationList);
								operationManager.execute();
								sourceFile.refreshLocal(IResource.DEPTH_ZERO, null);
								for(ConflictOperation c: conflictOperationList){
									
									IMarker marker = sourceFile.createMarker(IMarker.BOOKMARK);
									if(c instanceof RenameOperation){
										marker.setAttribute(IMarker.MESSAGE, "conflict resolution done by vcsn. Comment " + ((RenameOperation)c).getOriginIdentifier() + " is changed to " + ((RenameOperation)c).getModifiedIdentifier());
									}else if(c instanceof AddCommentOperation){
										marker.setAttribute(IMarker.MESSAGE, "conflict resolution done by vcsn. Comment " + c.getAppendComment() + " added.");
									}else if(c instanceof AddCastingOperation){
										marker.setAttribute(IMarker.MESSAGE, "conflict resolution done by vcsn. Cast to " + ((AddCastingOperation)c).getCastToType());
									}else if(c instanceof RemoveKeyWordOperation){
										marker.setAttribute(IMarker.MESSAGE, "conflict resolution done by vcsn. " + ((RemoveKeyWordOperation)c).getKeyword() + " is dropped.");
									}else if(c instanceof DropInterfaceTypeOperation){
										marker.setAttribute(IMarker.MESSAGE, "Refernce missing interface is dropped by vcsn.");
									}else if(c instanceof RefineParameterOperation){
										marker.setAttribute(IMarker.MESSAGE, "Parameter is refined by vcsn.");
									}
//									marker.setAttribute(IMarker.MESSAGE, "conflict resolution done by vcsn");
									marker.setAttribute(IMarker.SEVERITY, IMarker.SEVERITY_INFO);
									marker.setAttribute(IMarker.LINE_NUMBER, c.getConflictLocatedLineNumber());
								}
							}
							
							List<CommitPatch> existingPatchList = ExportImportHandler.importCommitPatch(System.getProperty(Config.homeDirectoryNotation) + File.separator + Config.incomingCommitPatchListFile, project.getName());
							existingPatchList.add(incomingPatch);
							ExportImportHandler.exportCommitPatchList(existingPatchList, project.getName());
							
							client = new ClientThread();
							localAddress = client.getLocalAddress(new File(project.getLocation().toString() + File.separator + Config.peerFile));
							client.writeHeaderIntoPackageFile(projectname, localClock, localAddress, Config.HeaderType.ACKNOWLEDGMENT);
							client.writeAcknowledgmentHeaderIntoPackageFile(Config.AcknowledgmentType.POSTPATCHSYNC);
							client.writeStringIntoPackageFile(configure.getUserName());
							client.writeStringIntoPackageFile(repository.getRef(Config.HEADHash).getObjectId().getName());
							client.writeStringIntoPackageFile(incomingPatch.getPatchHash());
							client.flush(new File(project.getProject().getLocation().toString() + File.separator + Config.peerFile));
							client.disposePackage();
						}catch(PatchApplyException pae){
							pae.printStackTrace();//.openInformation(PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell(), "Error", "Commit Patch " + incomingPatch.getPatchHash() + " in file: " + incomingPatch.getPatchPath() + " fail to apply with disable handled conflicts. Please apply it in manual.");
							mutex.release();
						}
						break;
				}
			}
			dataInputStream.close();
			mutex.release();
		} catch (IOException e) {
			e.printStackTrace();
		}catch (Exception ex){
			ex.printStackTrace();
		}finally{
			mutex.release();
		}
	}
	
	private void updatefileLock(IProject project, File receivedLockFile, VectorClock localClock){
		Configuration configure = new Configuration(project.getLocation().toString(), project.getName());
		FileLock fileLock = new FileLock(project.getLocation().toString() + File.separator + Config.lockFile);
		List<String> incomingLockList = fileLock.readLockedFileList(receivedLockFile.getAbsolutePath());
		fileLock.writeLockedFileList(incomingLockList);
		
		localClock.incrementClock(configure.getUserName() + Config.keyConnector + configure.getUserEmail());
		ExportImportHandler.exportVectorClock(localClock, project.getName());
	}
	
	private void updateLastAppliedCommitHash(String fileLocation, String commitAuthor, String incomingCommitHashString, String projectName){
		LastAppliedCommitHashList commithashList = ExportImportHandler.importLastAppliedCommitHashList(fileLocation, projectName);
		if(commithashList == null){
			commithashList = new LastAppliedCommitHashList();
		}
		commithashList.setCommitHash(commitAuthor, incomingCommitHashString);
		ExportImportHandler.exportLastAppliedCommitHashList(commithashList, projectName);
	}
	
	private File generateDiffPatch(Git git, String fromCommitHashString, String toCommitHashString){
		try{
		Repository repository = git.getRepository();
		ObjectReader reader = repository.newObjectReader();
		
		CanonicalTreeParser oldTreeIter = new CanonicalTreeParser();
		oldTreeIter.reset(reader, repository.resolve(fromCommitHashString + "^{tree}"));
		
		CanonicalTreeParser newTreeIter = new CanonicalTreeParser();
		newTreeIter.reset(reader, repository.resolve(toCommitHashString + "^{tree}"));
		
		OutputStream baos = new ByteArrayOutputStream();
		File fDiffPatch = vcsn.utils.Util.createTempCommitPatchFile();
		FileOutputStream fos = new FileOutputStream(fDiffPatch);
		
		List<DiffEntry> diffs = git.diff().setNewTree(newTreeIter).setOldTree(oldTreeIter).setOutputStream(baos).call();
		modifiedFileList = "";
		for(DiffEntry diff : diffs){
			if(diff.getNewPath().equals(Config.patchDeletion)){
				if(!modifiedFileList.contains(Config.patchDeletion + Config.separator)){
					modifiedFileList += diff.getNewPath() + Config.separator;
				}
			}else{
				modifiedFileList += diff.getNewPath() + Config.separator;
			}
		}
//		System.out.println(modifiedFileList);
		fos.write(baos.toString().getBytes());
		fos.flush();
		fos.close();
		baos.close();
		
		return fDiffPatch;
		}catch(IOException ioex){
			return null;
		}catch(GitAPIException gitex){
			return null;
		}
	}
	
	private Vector<String> getProcessingFileListFromConflictOperationList(List<ConflictOperation> operationList){
		Vector<String> processingFileList = new Vector<String>();
		for(ConflictOperation c : operationList){
			if(!processingFileList.contains(c.getConflictLocatedFileName())){
				processingFileList.add(c.getConflictLocatedFileName());
			}
		}
		return processingFileList;
	}
	
	private List<ConflictOperation> getConflictOperationListInSingleFile(List<ConflictOperation> operationList, String FileName){
		List<ConflictOperation> operationListInSingleFile = new ArrayList<ConflictOperation>();
		for(ConflictOperation c : operationList){
			if(c.getConflictLocatedFileName().equals(FileName)){
				operationListInSingleFile.add(c);
			}
		}
		return operationListInSingleFile;
	}
}
